Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.222 diff -u -p -r1.222 bootstrap.inc --- includes/bootstrap.inc 6 Sep 2008 15:20:09 -0000 1.222 +++ includes/bootstrap.inc 6 Sep 2008 19:04:26 -0000 @@ -907,7 +907,7 @@ function drupal_get_messages($type = NUL * @return bool * TRUE if access is denied, FALSE if access is allowed. */ -function drupal_is_denied($ip) { +function drupal_denied_ip($ip) { // Because this function is called on every page request, we first check // for an array of IP addresses in settings.php before querying the // database. @@ -922,6 +922,34 @@ function drupal_is_denied($ip) { } /** + * Check to see if a referrer has been blocked. + * + * Blocked referrers are stored in the database by default. However for + * performance reasons we allow an override in settings.php. This allows us + * to avoid querying the database at this critical stage of the bootstrap if + * an administrative interface for referrer blocking is not required. + * + * @param $referrer string + * Referrer to check. + * @return bool + * TRUE if access is denied, FALSE if access is allowed. + */ + +function drupal_denied_referrer($referrer) { + // Because this function is called on every page request, we first check + // for an array of IP addresses in settings.php before querying the + // database. + $blocked_referrers = variable_get('blocked_referrers', NULL); + if (isset($blocked_referrers) && is_array($blocked_referrers)) { + return in_array($referrer, $blocked_referrers); + } + else { + $sql = "SELECT 1 FROM {blocked_referrers} WHERE referrer = '%s'"; + return (bool) db_result(db_query($sql, $referrer)); + } +} + +/** * Generates a default anonymous $user object. * * @return Object - the user object. @@ -1018,12 +1046,19 @@ function _drupal_bootstrap($phase) { break; case DRUPAL_BOOTSTRAP_ACCESS: - // Deny access to blocked IP addresses - t() is not yet available. - if (drupal_is_denied(ip_address())) { + // Deny access to blocked IP addresses and referrers - t() is not yet available. + if (drupal_denied_ip(ip_address())) { header('HTTP/1.1 403 Forbidden'); print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.'; exit(); } + // Apache refers to referrer as referer [sic]. + elseif (drupal_denied_referrer(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST))) { + header('HTTP/1.0 403 Forbidden'); + print "Sorry, your referrer has been banned."; + exit(); + } + break; case DRUPAL_BOOTSTRAP_SESSION: Index: modules/statistics/statistics.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics/statistics.admin.inc,v retrieving revision 1.10 diff -u -p -r1.10 statistics.admin.inc --- modules/statistics/statistics.admin.inc 6 Sep 2008 08:36:21 -0000 1.10 +++ modules/statistics/statistics.admin.inc 6 Sep 2008 19:04:26 -0000 @@ -115,6 +115,7 @@ function statistics_top_referrers() { array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Url'), 'field' => 'url'), array('data' => t('Last visit'), 'field' => 'last'), + array('data' => t('Ban referrer')), ); $query .= tablesort_sql($header); @@ -122,11 +123,12 @@ function statistics_top_referrers() { $rows = array(); while ($referrer = db_fetch_object($result)) { - $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval($_SERVER['REQUEST_TIME'] - $referrer->last)))); + $referrer_parsed = parse_url($referrer->url, PHP_URL_HOST); + $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval($_SERVER['REQUEST_TIME'] - $referrer->last))), l(t("Ban Referrer"), "admin/settings/referrer-blocking/" . check_url($referrer_parsed))); } if (empty($rows)) { - $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3)); + $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4)); } $output = theme('table', $header, $rows); Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.87 diff -u -p -r1.87 system.admin.inc --- modules/system/system.admin.inc 6 Sep 2008 08:36:21 -0000 1.87 +++ modules/system/system.admin.inc 6 Sep 2008 19:04:26 -0000 @@ -1146,8 +1146,6 @@ function system_ip_blocking_form($form_s '#type' => 'submit', '#value' => t('Save'), ); - $form['#submit'][] = 'system_ip_blocking_form_submit'; - $form['#validate'][] = 'system_ip_blocking_form_validate'; return $form; } @@ -1197,6 +1195,93 @@ function system_ip_blocking_delete_submi } /** + * Menu callback. Display blocked referrers. + */ +function system_referrer_blocking() { + $output = ''; + $rows = array(); + $header = array(t('Referrer'), t('Operations')); + $result = db_query('SELECT * FROM {blocked_referrers}'); + while ($referrer = db_fetch_object($result)) { + $rows[] = array( + $referrer->referrer, + l(t('delete'), "admin/settings/referrer-blocking/delete/$referrer->rid"), + ); + } + + $output .= drupal_get_form('system_referrer_blocking_form'); + + $output .= theme('table', $header, $rows); + + return $output; +} + +/** + * Define the form for blocking referrers. + * + * @ingroup forms + * @see system_referrer_blocking_form_validate() + * @see system_referrer_blocking_form_submit() + */ +function system_referrer_blocking_form($form_state) { + $form['referrer'] = array( + '#title' => t('Referrer'), + '#type' => 'textfield', + '#size' => 64, + '#maxlength' => 255, + '#default_value' => arg(3), + '#description' => t('Enter a valid Referrer.'), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + return $form; +} + +function system_referrer_blocking_form_validate($form, &$form_state) { + $referrer = trim($form_state['values']['referrer']); + if (db_result(db_query("SELECT * FROM {blocked_referrers} WHERE referrer = '%s'", $referrer))) { + form_set_error('referrer', t('This referrer is already blocked.')); + } + else if ($referrer == $_SERVER['HTTP_HOST']) { + form_set_error('referrer', t('You may not block your own host.')); + } +} + +function system_referrer_blocking_form_submit($form, &$form_state) { + $referrer = trim($form_state['values']['referrer']); + db_query("INSERT INTO {blocked_referrers} (referrer) VALUES ('%s')", $referrer); + drupal_set_message(t('The referrer %referrer has been blocked.', array('%referrer' => $referrer))); + $form_state['redirect'] = 'admin/settings/referrer-blocking'; + return; +} + +/** + * Referrer deletion confirm page. + * + * @see system_referrer_blocking_delete_submit() + */ +function system_referrer_blocking_delete(&$form_state, $rid) { + $form['blocked_referrer'] = array( + '#type' => 'value', + '#value' => $rid, + ); + return confirm_form($form, t('Are you sure you want to delete %referrer?', array('%referrer' => $rid['referrer'])), 'admin/settings/referrer-blocking', t('This action cannot be undone.'), t('Delete'), t('Cancel')); +} + +/** + * Process system_referrer_blocking_delete form submissions. + */ +function system_referrer_blocking_delete_submit($form, &$form_state) { + $blocked_referrer = $form_state['values']['blocked_referrer']; + db_query("DELETE FROM {blocked_referrers} WHERE rid = %d", $blocked_referrer['rid']); + watchdog('user', 'Deleted %referrer', array('%referrer' => $blocked_referrer['referrer'])); + drupal_set_message(t('The referrer address %referrer was deleted.', array('%referrer' => $blocked_referrer['referrer']))); + $form_state['redirect'] = 'admin/settings/referrer-blocking'; +} + +/** * Form builder; The general site information form. * * @ingroup forms Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.263 diff -u -p -r1.263 system.install --- modules/system/system.install 6 Sep 2008 08:36:21 -0000 1.263 +++ modules/system/system.install 6 Sep 2008 19:04:27 -0000 @@ -568,6 +568,29 @@ function system_schema() { ), 'primary key' => array('iid'), ); + + $schema['blocked_referrers'] = array( + 'description' => t('Stores blocked referrers.'), + 'fields' => array( + 'rid' => array( + 'description' => t('Primary Key: unique ID for referrers.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'referrer' => array( + 'description' => t('Referrer'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'indexes' => array( + 'blocked_referrer' => array('referrer'), + ), + 'primary key' => array('rid'), + ); $schema['cache'] = array( 'description' => t('Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.'), @@ -3049,6 +3072,40 @@ function system_update_7010() { } /** + * Add the ability to perform blocking of traffic based on referrer. + */ +function system_update_7011() { + $ret = array(); + + $schema['blocked_referrers'] = array( + 'description' => t('Stores blocked referrers.'), + 'fields' => array( + 'rid' => array( + 'description' => t('Primary Key: unique ID for referrers.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'referrer' => array( + 'description' => t('Referrer'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'indexes' => array( + 'blocked_referrer' => array('referrer'), + ), + 'primary key' => array('rid'), + ); + + db_create_table($ret, 'blocked_referrers', $schema['blocked_referrers']); + + return $ret; +} + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.617 diff -u -p -r1.617 system.module --- modules/system/system.module 6 Sep 2008 08:36:21 -0000 1.617 +++ modules/system/system.module 6 Sep 2008 19:04:27 -0000 @@ -171,7 +171,7 @@ function system_perm() { 'access site reports' => t('View reports from system logs and other status information.'), 'select different theme' => t('Select a theme other than the default theme set by the site administrator.'), 'administer files' => t('Manage user-uploaded files.'), - 'block IP addresses' => t('Block IP addresses from accessing your site.'), + 'block traffic' => t('Block IP addresses and/or referrers from accessing your site.'), ); } @@ -503,22 +503,44 @@ function system_menu() { 'title' => 'IP address blocking', 'description' => 'Manage blocked IP addresses.', 'page callback' => 'system_ip_blocking', - 'access arguments' => array('block IP addresses'), + 'access arguments' => array('block traffic'), ); $items['admin/settings/ip-blocking/%'] = array( 'title' => 'IP address blocking', 'description' => 'Manage blocked IP addresses.', 'page callback' => 'system_ip_blocking', - 'access arguments' => array('block IP addresses'), + 'access arguments' => array('block traffic'), 'type' => MENU_CALLBACK, ); $items['admin/settings/ip-blocking/delete/%blocked_ip'] = array( 'title' => 'Delete IP address', 'page callback' => 'drupal_get_form', 'page arguments' => array('system_ip_blocking_delete', 4), - 'access arguments' => array('block IP addresses'), + 'access arguments' => array('block traffic'), 'type' => MENU_CALLBACK, ); + + // Referrer blocking. + $items['admin/settings/referrer-blocking'] = array( + 'title' => 'Referrer blocking', + 'description' => 'Manage blocked referrers.', + 'page callback' => 'system_referrer_blocking', + 'access arguments' => array('block traffic'), + ); + $items['admin/settings/referrer-blocking/%'] = array( + 'title' => 'Referrer blocking', + 'description' => 'Manage blocked referrers.', + 'page callback' => 'system_referrer_blocking', + 'access arguments' => array('block traffic'), + 'type' => MENU_CALLBACK, + ); + $items['admin/settings/referrer-blocking/delete/%blocked_referrer'] = array( + 'title' => 'Delete referrer', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_referrer_blocking_delete', 4), + 'access arguments' => array('block traffic'), + 'type' => MENU_CALLBACK, + ); // Settings: $items['admin/settings/site-information'] = array( @@ -671,6 +693,21 @@ function blocked_ip_load($iid) { } /** + * Retrieve a blocked referrer from the database. + * + * @param $rid integer + * The ID of the blocked referrer to retrieve. + * + * @return + * The blocked referrer from the database as an array. + */ +function blocked_referrer_load($rid) { + $blocked_referrer = db_fetch_array(db_query("SELECT * FROM {blocked_referrers} WHERE rid = %d", $rid)); + return $blocked_referrer; +} + + +/** * Menu item access callback - only admin or enabled themes can be accessed. */ function _system_themes_access($theme) { @@ -2019,11 +2056,23 @@ function system_goto_action($object, $co */ function system_block_ip_action() { $ip = ip_address(); - db_query("INSERT INTO {blocked_ips} (ip) VALUES ('%s')", $ip);; + db_query("INSERT INTO {blocked_ips} (ip) VALUES ('%s')", $ip); watchdog('action', 'Banned IP address %ip', array('%ip' => $ip)); } /** + * Implementation of a Drupal action. + * Blocks the user's referrer. + */ +function system_block_referrer_action() { + // Apache spells referrer, referer. + $referrer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); + db_query("INSERT INTO {blocked_referrers} (referrer) VALUES ('%s')", $referrer); + watchdog('action', 'Banned IP address %referrer', array('%referrer' => $referrer)); +} + + +/** * Generate an array of time zones and their local time&date. */ function _system_zonelist() {